﻿using System;
using System.Linq;
using System.Text;
using SS.Net.AppGen.Generators;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using SS.Net.AppGen.Models;
using SS.Common.DBBuilder.DBDataStructure;
using SS.Common.Logging;
using SS.Net.AppGen.Utils;

namespace SS.Net.AppGen.WebGenerators.WebControllerGenerator
{
    /// <summary>
    /// The base class for generating partials of controller
    /// </summary>
    public abstract class PartialControllerGenerator : BaseGenerator
    {
        #region Fields

        private static class Consts
        {
            /// <summary>
            /// Valid action types
            /// </summary>
            public static readonly string[] ValidActionType = { "Delete", "DeleteWithoutAjax", "Update", "Insert" };

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstDeleteContents = "[DeleteContents]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstUpdateContents = "[UpdateContents]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstInsertContents = "[InsertContents]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstForeignViewModels = "[ForeignViewModels]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstUpdateSelectToPage = "[UpdateSelectToPage]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstMainForeignKeyArg = "[MainForeignKeyArg]";
            public const string ConstDeleteRangeOfMainForeignKeyArg = "[DeleteRangeOfMainForeignKeyArg]";

            public const string ConstEditArgs = "[EditArgs]";
            public const string ConstCancelEditingArgs = "[CancelEditingArgs]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstMainForeignKeyArgWithoutCommas = "[MainForeignKeyArgWithoutCommas]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstMainForeignKeyParsing = "[MainForeignKeyParsing]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstLowerPrimaryKey = "[LowerPrimaryKey]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstParsingLowerPimaryKey = "[ParsingLowerPimaryKey]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstParsingPimaryKey = "[ParsingPimaryKey]";
            public const string ConstRedirectFromUpdateAction = "[RedirectFromUpdateAction]";
            public const string ConstRedirectFromInsertAction = "[RedirectFromInsertAction]";
        }

        /// <summary>
        /// The instance of ClassComponents, this object will be updated when deserialization pattern file
        /// </summary>
        private ClassComponents _classComponents;

        /// <summary>
        /// The image attribute in a table
        /// </summary>
        protected string _imgAttr = string.Empty;

        /// <summary>
        /// The variable presents the algorithm to generate Delete method in partial controller
        /// </summary>
        protected IDeleteControllerItemAlgorithm _deleteAlgorithm;

        /// <summary>
        /// The variable presents the algorithm to generate Update method in partial controller
        /// </summary>
        protected IUpdateControllerItemAlgorithm _updateAlgorithm;

        /// <summary>
        /// The variable presents the algorithm to generate Insert method in partial controller
        /// </summary>
        protected IInsertControllerItemAlgorithm _insertAlgorithm;

        /// <summary>
        /// The logger instance
        /// </summary>
        private readonly ILogger Logger = LoggerManager.GetLogger(typeof(PartialControllerGenerator).Name);

        #endregion

        #region Methods

        /// <summary>
        /// Deserialize Base Controller pattern file
        /// </summary>
        public void DeserializeTemplate()
        {
            var patternFile = Path.Combine(this.GetAppPath(), "Templates", "WebTemplates", this.XMLTemplate);

            using (XmlTextReader xreader = new XmlTextReader(patternFile))
            {
                xreader.Namespaces = false;
                try
                {
                    var x = new XmlSerializer(typeof (ClassComponents));
                    this._classComponents = x.Deserialize(xreader) as ClassComponents;
                }
                catch (Exception ex)
                {
                    throw new Exception(string.Format(
                        "Cannot deserialize ControllerTemplate, ControllerTemplate => {0}", ex.Message));
                }
            }
        }

        /// <summary>
        /// Method for generating contents of controller components and save generated contents to a file
        /// </summary>
        /// <param name="tbl">The data source is corresponding with class which will be generated</param>
        public void GenerateContents(Table tbl)
        {
            this.Logger.InfoFormat("Generate Controller[{0}] --> Start", tbl.Name);

            try
            {
                var contents = new StringBuilder(BaseConsts.MAX_BYTES);

                // Generating Using contents
                contents.AppendLine(this._classComponents.UsingPart.Replace(BaseConsts.ConstGlobalNameSpace,
                                                                            this.GlobalNameSpace));
                contents.AppendLine(string.Empty);

                // Generating Class contents
                contents.AppendLine(string.Format("namespace {0}.Controllers", WebConfiguration.Instance.NameSpace));
                contents.Append("{");
                contents.AppendLine(this._classComponents.ClassPart.Comment.Replace(BaseConsts.ConstTable, tbl.Name));
                contents.AppendLine(BaseConsts.TAB1 + string.Format("public class {0}ManagerController : BaseController", tbl.Name));
                contents.AppendLine(BaseConsts.TAB1 + "{");

                // Generating Regions contents
                contents.Append(this.GenerateRegions(tbl));

                contents.AppendLine(BaseConsts.TAB1 + "}");
                contents.Append("}");
                contents.AppendLine();

                // Save controller contents to a file
                this.SaveFile(contents.ToString().TrimStart(), string.Format("{0}ManagerController.cs", tbl.Name));
            }
            catch (Exception ex)
            {
                this.Logger.ErrorFormat("GenerateContents Error: {0}", ex.ToString());
                throw new Exception(ex.ToString());
            }

            this.Logger.InfoFormat("Generate Controller[{0}] <-- End", tbl.Name);
        }

        /// <summary>
        /// The method for generating regions
        /// </summary>
        /// <param name="tbl">Data source for generating</param>
        /// <returns></returns>
        private string GenerateRegions(Table tbl)
        {
            var itemContents = string.Empty;
            var contents = new StringBuilder(BaseConsts.MAX_BYTES);
            var primaryKey = tbl.Attributes.FirstOrDefault(n => n.IsPrimaryKey);
            var foreignKeys = tbl.Attributes.Where(n => n.IsForeignKey);

            // Generating Regions
            foreach (Region region in this._classComponents.ClassPart.Regions)
            {
                contents.AppendLine(BaseConsts.TAB2 + string.Format("#region {0}", region.RegionName));
                contents.AppendLine(string.Empty);

                // Generating Item
                foreach (Item item in region.Items)
                {
                    if (item.IsFKPattern == null)
                    {
                        if (primaryKey != null)
                        {
                            itemContents = item.Content.Replace(BaseConsts.ConstTable, tbl.Name)
                                .Replace(BaseConsts.ConstPrimaryKey, primaryKey.Name)
                                .Replace(BaseConsts.ConstPluralizeTable, tbl.Name.Pluralize())
                                .Replace(BaseConsts.ConstArgs, this.BuildArgs(primaryKey))
                                .Replace(Consts.ConstLowerPrimaryKey, primaryKey.Name.ToLower())
                                .Replace(Consts.ConstParsingLowerPimaryKey,
                                         this.BuildParsingDataAndConvertToLower(primaryKey))
                                .Replace(Consts.ConstForeignViewModels, this.BuildForeignViewModels(tbl))
                                .Replace(Consts.ConstUpdateSelectToPage, this.BuildUpdateSelectToPage(tbl))
                                .Replace(Consts.ConstMainForeignKeyArg, this.BuildMainForeignKeyArgs(tbl))
                                .Replace(Consts.ConstDeleteRangeOfMainForeignKeyArg, this.BuildDeleteRangeOfMainForeignKeyArg(tbl))
                                .Replace(Consts.ConstEditArgs, this.BuildEditArgs(tbl))
                                .Replace(Consts.ConstCancelEditingArgs, this.BuildEditArgs(tbl))
                                .Replace(Consts.ConstMainForeignKeyArgWithoutCommas,
                                         this.BuildMainForeignKeyArgsWithoutCommas(tbl))
                                .Replace(Consts.ConstMainForeignKeyParsing, this.BuildMainForeignKeyParsing(tbl))
                                .Replace(BaseConsts.ConstImage, this._imgAttr)
                                .Replace(Consts.ConstRedirectFromUpdateAction, this.BuildRedirectFromUpdateAction(tbl))
                                .Replace(Consts.ConstRedirectFromInsertAction, this.BuildRedirectFromInsertAction(tbl))
                                .Replace(Consts.ConstParsingPimaryKey, this.BuildParsingDataWithoutLower(primaryKey));
                        }
                        contents.AppendLine(this.UpdateItemContents(itemContents, item, tbl));
                        contents.AppendLine(string.Empty);
                    }
                    else
                    {
                        bool boolVal;

                        if (bool.TryParse(item.IsFKPattern, out boolVal))
                        {
                            if (!boolVal)
                            {
                                itemContents = item.Content.Replace(BaseConsts.ConstTable, tbl.Name)
                                    .Replace(BaseConsts.ConstPrimaryKey, primaryKey.Name)
                                    .Replace(BaseConsts.ConstPluralizeTable, tbl.Name.Pluralize())
                                    .Replace(BaseConsts.ConstArgs, this.BuildArgs(primaryKey))
                                    .Replace(Consts.ConstLowerPrimaryKey, primaryKey.Name.ToLower())
                                    .Replace(Consts.ConstParsingLowerPimaryKey, this.BuildParsingData(primaryKey));
                                contents.AppendLine(this.UpdateItemContents(itemContents, item, tbl));
                                contents.AppendLine(string.Empty);
                            }
                            else
                            {
                                // It's foreign key pattern
                                foreach (SS.Common.DBBuilder.DBDataStructure.Attribute fk in foreignKeys)
                                {
                                    // Skips a refer table if it is existent in Skip array
                                    if (!this.IsItemExistent(BaseConsts.SkipTables, fk.ReferTo))
                                    {
                                        itemContents = item.Content.Replace(BaseConsts.ConstTable, tbl.Name)
                                            .Replace(BaseConsts.ConstPrimaryKey, primaryKey.Name)
                                            .Replace(BaseConsts.ConstPluralizeTable, tbl.Name.Pluralize())
                                            .Replace(BaseConsts.ConstPluralizeForeignTable, fk.ReferTo.Equals(WebConfiguration.Instance.Settings.UserTable) ? ("Custom" + fk.ReferTo).Pluralize() : fk.ReferTo.Pluralize())
                                            .Replace(BaseConsts.ConstForeignTable, fk.ReferTo)
                                            .Replace(BaseConsts.ConstForeignTableModel,
                                                     WebConfiguration.Instance.Settings.UserTable.Equals(fk.ReferTo)
                                                         ? "Custom" + fk.ReferTo
                                                         : fk.ReferTo)
                                            .Replace(BaseConsts.ConstArgs, this.BuildArgs(fk))
                                            .Replace(BaseConsts.ConstForeignKey, fk.Name);
                                        contents.AppendLine(this.UpdateItemContents(itemContents, item, tbl));
                                        contents.AppendLine(string.Empty);
                                    }
                                }
                            }
                        }
                        else
                        {
                            throw new Exception(string.Format("Cannot parse bool of item {0} in region {1}",
                                                              item.Content, region.RegionName));
                        }
                    }
                }

                contents.AppendLine(BaseConsts.TAB2 + "#endregion");
                contents.AppendLine(string.Empty);
            }
            return contents.ToString();
        }

        /// <summary>
        /// The method to update item contents
        /// </summary>
        /// <param name="orgContents">The original contents</param>
        /// <param name="item">The item will be generated</param>
        /// <param name="tbl">The data source is corresponding with class which will be generated</param>
        /// <returns></returns>
        private string UpdateItemContents(string orgContents, Item item, Table tbl)
        {
            var result = orgContents;
            if (item.ActionType != null)
            {
                // The action type is invalid
                if (Consts.ValidActionType.Any(
                    n => string.Equals(n, item.ActionType, StringComparison.OrdinalIgnoreCase)))
                {
                    switch (item.ActionType.ToUpper())
                    {
                        case "DELETE":
                        case "DELETEWITHOUTAJAX":
                            result = orgContents.Replace(Consts.ConstDeleteContents,
                                                       this._deleteAlgorithm.GenerateContents(item, tbl));
                            break;

                        case "UPDATE":
                            result = orgContents.Replace(Consts.ConstUpdateContents,
                                                       this._updateAlgorithm.GenerateContents(item, tbl));
                            break;

                        case "INSERT":
                            result = orgContents.Replace(Consts.ConstInsertContents,
                                                       this._insertAlgorithm.GenerateContents(item, tbl));
                            break;
                    }
                }
                else
                {
                    throw new Exception(string.Format("The ActionType {0} is invalid", item.ActionType));
                }
            }
            return result;
        }

        #endregion
    }
}
